import { useResizeObserver } from '@vueuse/core'
import * as THREE from 'three'
// 控制器
import { OrbitControls } from 'three/addons/controls/OrbitControls'
import { MapControls } from 'three/addons/controls/MapControls'
import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls'
// 加载器
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader'
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader'
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js'
// 单击选中模型高亮显示/选中模型发光
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer'
import { RenderPass } from 'three/addons/postprocessing/RenderPass'
import { OutlinePass } from 'three/addons/postprocessing/OutlinePass'
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass'
import { FXAAShader } from 'three/addons/shaders/FXAAShader'
// label标签
import { CSS2DObject, CSS2DRenderer } from 'three/addons/renderers/CSS2DRenderer'
import { CSS3DObject, CSS3DRenderer, CSS3DSprite } from 'three/addons/renderers/CSS3DRenderer'

// TWEEN 动画
import { cameraControlsTwwen, cameraTwwen, TWEEN } from './animations'

// 加载器
const GLTFloaderFn = new GLTFLoader()
GLTFloaderFn.setDRACOLoader(new DRACOLoader().setDecoderPath('/three/gltf/'))

// 贴图加载器
const textureLoader = new THREE.TextureLoader()

// 时钟
const clock = new THREE.Clock()

// 场景参数
let keyType = ref('')

/**
 * 创建渲染器
 * @param {dom元素} container
 */
function rendererFn(container) {
	// 渲染器 // antialias开启抗锯齿 alpha开启背景透明
	const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
	renderer.outputColorSpace = THREE.SRGBColorSpace
	renderer.setPixelRatio(window.devicePixelRatio)
	container.value.appendChild(renderer.domElement)
	return renderer
}

/**
 * 创建label渲染器
 * @param {dom元素} container
 */
function labelRendererFn(container, CSS3D = true) {
	let labelRenderer
	if (CSS3D) {
		labelRenderer = new CSS3DRenderer()
	} else {
		labelRenderer = new CSS2DRenderer()
	}
	labelRenderer.domElement.style.position = 'absolute'
	labelRenderer.domElement.style.top = 0
	labelRenderer.domElement.style.pointerEvents = 'none'
	container.value.appendChild(labelRenderer.domElement)
	return labelRenderer
}

// 模型居中显示
function setContent(scene) {
	// 获得包围盒得min和max
	const box = new THREE.Box3().setFromObject(scene)
	// 返回包围盒的中心点
	const center = box.getCenter(new THREE.Vector3())
	scene.position.x += scene.position.x - center.x
	scene.position.y += scene.position.y - center.y
	scene.position.z += scene.position.z - center.z
}

// 打印相机位置和朝向  相机朝向也可以使用 controls.target 获取
function cameraDistanceFn(camera) {
	// 相机朝向
	const distance = camera.position.length()
	const { x, y, z } = camera.position
	const direction = camera.getWorldDirection(new THREE.Vector3())
	const target = new THREE.Vector3(x, y, z).add(direction.multiplyScalar(distance))
	console.log('target', target)
	// 相机位置
	console.log('position', camera.position)
}

// 相机漫游
function cameraFn(camera) {
	/**
	 * 第一人称漫游视角
	 */
	let up = false, //是否向前运行
		down = false, //是否向后运行
		left = false, //是否向左运行
		right = false, //是否向右运行
		keyType = '' // 按钮类型
	document.addEventListener('keydown', keydownFn, false)
	document.addEventListener('keyup', keyupFn, false)

	function keydownFn(event) {
		switch (event.keyCode) {
			case 38: // up
			case 87: // w
				up = true
				keyType = 'up'
				break
			case 37: // left
			case 65: // a
				left = true
				keyType = 'left'
				break
			case 40: // down
			case 83: // s
				down = true
				keyType = 'down'
				break
			case 39: // right
			case 68: // d
				right = true
				keyType = 'right'
				break
		}
	}
	function keyupFn(event) {
		keyType = ''
		switch (event.keyCode) {
			case 38: // up
			case 87: // w
				up = false
				break
			case 37: // left
			case 65: // a
				left = false
				break
			case 40: // down
			case 83: // s
				down = false
				break
			case 39: // right
			case 68: // d
				right = false
				break
		}
	}
	//#region 相机旋转
	let isClicked = false
	document.addEventListener('mousedown', () => {
		isClicked = true
	})
	document.addEventListener('mouseup', () => {
		isClicked = false
	})
	document.addEventListener('mousemove', MouseMove, false)

	let minPolarAngle = 0 // radians
	let maxPolarAngle = Math.PI // radians
	let pointerSpeed = 1.0
	const _PI_2 = Math.PI / 2
	// 欧拉角描述一个旋转变换，通过指定轴顺序和其各个轴向上的指定旋转角度来旋转一个物体。
	const _euler = new THREE.Euler(0, 0, 0, 'YXZ')

	//相机旋转
	function MouseMove(event) {
		event.preventDefault()
		if (!isClicked) return
		const movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0
		const movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0
		_euler.setFromQuaternion(camera.quaternion)
		_euler.y -= movementX * 0.002 * pointerSpeed
		_euler.x += movementY * 0.002 * pointerSpeed
		_euler.x = Math.max(_PI_2 - maxPolarAngle, Math.min(_PI_2 - minPolarAngle, _euler.x))
		camera.quaternion.setFromEuler(_euler)
	}

	// 相机碰撞检测，移动
	let direction = new THREE.Vector3()
	function cameraRender(objects, speed = 0.2) {
		camera.getWorldDirection(direction)
		direction.y = 0
		let position = camera.position.clone()
		if (up) {
			position.add(direction.multiplyScalar(speed))
		}
		if (down) {
			position.add(direction.multiplyScalar(-speed))
		}
		if (left) {
			direction.cross(camera.up)
			position.add(direction.multiplyScalar(-speed))
		}
		if (right) {
			direction.cross(camera.up)
			position.add(direction.multiplyScalar(speed))
		}
		if (!collideCheck(objects, position)) {
			camera.position.set(position.x, position.y, position.z)
		}
		return keyType
	}
	return cameraRender
}

/**
 * objects 障碍物对象
 * position 位置
 * 判断附近有没有障碍物 返回true,false
 */
function collideCheck(objects, position) {
	const arr = [new THREE.Vector3(1, 0, 0), new THREE.Vector3(-1, 0, 0), new THREE.Vector3(0, 0, 1), new THREE.Vector3(0, 0, -1)]
	for (let i = 0, len = arr.length; i < len; i++) {
		const raycaster = new THREE.Raycaster(position, arr[i], 0, 1)
		const intersections = raycaster.intersectObjects(objects, false)
		if (intersections.length) return true
	}
	return false
}

// 获取模型动画
function mixersFn(object) {
	const animations = object.animations
	const mixer = new THREE.AnimationMixer(object.scene)
	const actions = []
	for (var i = 0; i < animations.length; i++) {
		actions.push(mixer.clipAction(animations[i]))
	}
	actions.forEach((action) => action.play())
	return mixer
}

/**
 * 根据当前相机位置旋转罗盘
 */
function compassRotate(camera) {
	// 获取相机方向向量 世界坐标
	var direction = camera.getWorldDirection(new THREE.Vector3())
	// 将该向量转换为单位向量（unit vector）， 也就是说，将该向量的方向设置为和原向量相同，但是其长度（length）为1。
	var dir = new THREE.Vector2(direction.x, direction.z).normalize()
	// dir.angle () 计算该向量相对于x轴正方向的弧度角度。
	var azimuthZ = 360 - (90 + (dir.angle() * 180) / Math.PI)
	var compass = document.getElementById('compass-pointer')
	if (compass) compass.style.transform = `rotateZ(${azimuthZ}deg)`
}

/**
 * 加载网格底图
 */
function initGrid(scene) {
	var size = 3000
	var step = 300
	var gridHelper = new THREE.GridHelper(size, step)
	gridHelper.material.transparent = true // 是否透明
	gridHelper.material.opacity = 0.5 // 透明度
	scene.add(gridHelper)
}

/**
 * 创建透明墙体材质
 * option =>
 * params height color opacity flicker是否闪烁
 * **/
function createOpacityWallMat({ height = 10, color = '#00ffff', opacity = 0.5, flicker = true } = {}) {
	// 顶点着色器
	const vertexShader = flicker
		? `
          uniform float time;
          uniform float u_height;
          varying float v_opacity;
          void main() {
            vec3 vPosition = position;
            v_opacity = mix(1.0, 0.0, position.y / u_height * 1.0) * (1.0 + sin(time) * 0.5);
            gl_Position = projectionMatrix * modelViewMatrix * vec4(vPosition, 1);
          }
       `
		: `
          uniform float u_height;
          varying float v_opacity;
          void main() {
            vec3 vPosition = position;
            v_opacity = 1.0 - (position.y / u_height * 1.0);
            gl_Position = projectionMatrix * modelViewMatrix * vec4(vPosition, 1);
          }
       `
	// 片元着色器
	const fragmentShader = `
          uniform vec3 u_color;
          uniform float u_opacity;
          varying float v_opacity;
          void main() {
              gl_FragColor = vec4(u_color, v_opacity * u_opacity);
          }
        `

	return new THREE.ShaderMaterial({
		uniforms: {
			u_height: {
				value: height
			},
			u_opacity: {
				value: opacity
			},
			u_color: {
				value: new THREE.Color(color)
			},
			time: {
				value: 0
			}
		},
		transparent: true,
		depthWrite: false,
		depthTest: false,
		side: THREE.DoubleSide,
		vertexShader,
		fragmentShader
	})
}

/**
 * 创建流体墙体材质
 * option =>
 * params bgUrl flowUrl
 * **/
function createFlowWallMat({ height = 10, color = '#00ffff', opacity = 0.8, flowUrl } = {}) {
	// 顶点着色器
	const vertexShader = `
            varying vec2 vUv;
            uniform float u_height;
            varying float v_opacity;
            void main() {
                vUv = uv;
                vec3 vPosition = position;
                v_opacity = 1.0 - (position.y / u_height * 1.0);
                gl_Position = projectionMatrix * modelViewMatrix * vec4(vPosition, 1);
            }
        `
	// 片元着色器
	const fragmentShader = `
            varying vec2 vUv;
            uniform float time;
            uniform vec3 u_color;
            uniform float u_opacity;
            varying float v_opacity;
            uniform sampler2D flowTexture;
            void main() {
                vec4 colora = texture2D( flowTexture, vec2( vUv.x, fract(vUv.y + time )));
                vec4 colorb = vec4(u_color, v_opacity * u_opacity);
                gl_FragColor = colorb + colora * colorb;
            }
        `
	const flowTexture = textureLoader.load(flowUrl || `${import.meta.env.VITE_APP_BASE_BASE || '/'}model/flowTexture.png`)
	// 允许平铺
	flowTexture.wrapS = flowTexture.wrapT = THREE.RepeatWrapping

	return new THREE.ShaderMaterial({
		uniforms: {
			u_height: {
				value: height
			},
			u_opacity: {
				value: opacity
			},
			time: {
				value: 0
			},
			flowTexture: {
				value: flowTexture
			},
			u_color: {
				value: new THREE.Color(color)
			}
		},
		transparent: true,
		depthWrite: false,
		depthTest: false,
		side: THREE.DoubleSide,
		vertexShader,
		fragmentShader
	})
}

/**
 * 通过path构建墙体
 * height
 * path
 * material
 * expand(是否需要扩展路径)
 * **/
function creatWallByPath({ height = 10, path = [], material, expand = true } = {}) {
	let verticesByTwo = null
	// 1.处理路径数据  每两个顶点为为一组
	if (expand) {
		// 1.1向y方向拉伸顶点
		verticesByTwo = path.reduce((arr, [x, y, z]) => {
			return arr.concat([
				[
					[x, y, z],
					[x, y + height, z]
				]
			])
		}, [])
	} else {
		// 1.2 已经处理好路径数据
		verticesByTwo = path
	}
	// 2.解析需要渲染的四边形 每4个顶点为一组
	const verticesByFour = verticesByTwo.reduce((arr, item, i) => {
		if (i === verticesByTwo.length - 1) return arr
		return arr.concat([[item, verticesByTwo[i + 1]]])
	}, [])
	// 3.将四边形面转换为需要渲染的三顶点面
	const verticesByThree = verticesByFour.reduce((arr, item) => {
		const [[point1, point2], [point3, point4]] = item
		return arr.concat(...point2, ...point1, ...point4, ...point1, ...point3, ...point4)
	}, [])
	const geometry = new THREE.BufferGeometry()
	// 4. 设置position
	const vertices = new Float32Array(verticesByThree)
	geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3))
	// 5. 设置uv 6个点为一个周期 [0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1]

	// 5.1 以18个顶点为单位分组
	const pointsGroupBy18 = new Array(verticesByThree.length / 3 / 6).fill(0).map((item, i) => {
		return verticesByThree.slice(i * 3 * 6, (i + 1) * 3 * 6)
	})
	// 5.2 按uv周期分组
	const pointsGroupBy63 = pointsGroupBy18.map((item, i) => {
		return new Array(item.length / 3).fill(0).map((it, i) => item.slice(i * 3, (i + 1) * 3))
	})
	// 5.3根据BoundingBox确定uv平铺范围
	geometry.computeBoundingBox()
	const { min, max } = geometry.boundingBox
	const rangeX = max.x - min.x
	const uvs = [].concat(
		...pointsGroupBy63.map((item) => {
			const point0 = item[0]
			const point5 = item[5]
			const distance = new THREE.Vector3(...point0).distanceTo(new THREE.Vector3(...point5)) / (rangeX / 10)
			return [0, 1, 0, 0, distance, 1, 0, 0, distance, 0, distance, 1]
		})
	)
	geometry.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(uvs), 2))
	const meshMat =
		material ||
		new THREE.MeshBasicMaterial({
			color: 0x00ffff,
			side: THREE.DoubleSide
		})
	return new THREE.Mesh(geometry, meshMat)
}

/**
 * 创建平面扩散波
 * width
 * color
 * position
 * uSpeed
 * uSge
 */
function creatDiffusion({ width = 2, color = '#00ffff', uSpeed = 0.1, uSge = 4, position } = {}) {
	const vertexShader = `
        varying vec2 vUv;
        void main() { 
            vUv = uv;
            gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
        }
    `
	// const fragmentShader = `
	//     varying vec2 vUv;
	//     uniform vec3 uColor;
	//     uniform float uOpacity;
	//     uniform float uSpeed;
	//     uniform float uSge;
	//     uniform float time;
	//     float PI = 3.14159265;

	//     float drawCircle(float index, float range) {
	//         float opacity = 1.0;
	//         if (index >= 1.0 - range) {
	//             opacity = 1.0 - (index - (1.0 - range)) / range;
	//         } else if(index <= range) {
	//             opacity = index / range;
	//         }
	//         return opacity;
	//     }
	//     float distanceTo(vec2 src, vec2 dst) {
	//         float dx = src.x - dst.x;
	//         float dy = src.y - dst.y;
	//         float dv = dx * dx + dy * dy;
	//         return sqrt(dv);
	//     }
	//     void main() {
	//         float time = -time * uSpeed;
	//         float opacity = 0.0;
	//         float len = distanceTo(vec2(0.5, 0.5), vec2(vUv.x, vUv.y));

	//         float size = 1.0 / uSge;
	//         vec2 range = vec2(0.65, 0.75);
	//         float index = mod(time + len, size);

	//         if (index < size && len <= 0.5) {
	//             float i = sin(index / size * PI);
	//             // 处理边缘锯齿
	//             if (i >= range.x && i <= range.y){
	//                 // 归一
	//                 float t = (i - range.x) / (range.y - range.x);
	//                 // 边缘锯齿范围
	//                 float r = 0.3;
	//                 opacity = drawCircle(t, r);
	//             }
	//             // 渐变
	//             opacity *=  1.0 - len / 0.5;
	//         };
	//         gl_FragColor = vec4(uColor, uOpacity * opacity);
	// }
	// `;

	const fragmentShader = `
        uniform float time; 
		varying vec2 vUv;  
        vec3 hsb2rgb(in vec3 c){
            vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0,0.0,1.0);
            rgb = rgb*rgb*(3.0-2.0*rgb);
            return c.z * mix( vec3(1.0), rgb, c.y);
        }
        void main(void) {
            // float time = time*1.;
            vec2 uv = (vUv - 0.5) * 4.0;
            float r = length(uv) * 0.9;
            vec3 color = hsb2rgb(vec3(0.24, 0.7, 0.4));
            
            float a = pow(r, 2.0);
            float b = sin(r * 0.8 - 1.6);
            float c = sin(r - 0.010);
            float s = sin(a - time * 3.0 + b) * c;
            
            color *= abs(1.0 / (s * 10.8)) - 0.01;
            if(color[0] < 0.3){
                gl_FragColor = vec4(color, 0);
            }else{
                gl_FragColor = vec4(color, 1);
            }
        }
	`
	const geometry = new THREE.CircleGeometry(width)
	//创建RawShaderMaterial着色器材质
	const material = new THREE.ShaderMaterial({
		uniforms: {
			uColor: { value: new THREE.Color(color) },
			uOpacity: { value: 1 },
			uSpeed: { value: uSpeed },
			uSge: { value: uSge },
			uRadius: { value: width / 2 },
			time: { value: 0 }
		},
		transparent: true,
		depthWrite: false,
		depthTest: false,
		side: THREE.DoubleSide,
		vertexShader,
		fragmentShader
	})
	const planeMesh = new THREE.Mesh(geometry, material)
	planeMesh.position.set(...position)
	planeMesh.rotation.x = 0.5 * Math.PI
	return planeMesh
}

/**
 * 创建火焰效果
 */

function creatFire({ width = 2, position } = {}) {
	const vertexShader = `
        varying vec2 vUv;
        void main() { 
            vUv = uv;
            gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
        }
    `

	const fragmentShader = `
    	uniform float time;
    	varying vec2 vUv;
        float snoise(vec3 uv, float res){
            const vec3 s = vec3(1e0, 1e2, 1e3);
            uv *= res;
            vec3 uv0 = floor(mod(uv, res))*s;
            vec3 uv1 = floor(mod(uv+vec3(1.), res))*s;

            vec3 f = fract(uv); f = f*f*(3.0-2.0*f);

            vec4 v = vec4(uv0.x+uv0.y+uv0.z, uv1.x+uv0.y+uv0.z,
                        uv0.x+uv1.y+uv0.z, uv1.x+uv1.y+uv0.z);

            vec4 r = fract(sin(v*1e-1)*1e3);
            float r0 = mix(mix(r.x, r.y, f.x), mix(r.z, r.w, f.x), f.y);

            r = fract(sin((v + uv1.z - uv0.z)*1e-1)*1e3);
            float r1 = mix(mix(r.x, r.y, f.x), mix(r.z, r.w, f.x), f.y);

            return mix(r0, r1, f.z)*2.-1.;
        }

        void main(void) {

            vec2 p = (vUv - 0.5) * 2.0;

            float color = 3.0 - (3.*length(2.*p));

            vec3 coord = vec3(atan(p.x,p.y)/6.2832+.5, length(p)*.4, .5);

            for(int i = 1; i <= 7; i++){
                float power = pow(2.0, float(i));
                color += (1.5 / power) * snoise(coord + vec3(0.,-time*.05, time*.01), power*16.);
            }
            if(color <= 0.0){
                gl_FragColor = vec4( color, pow(max(color,0.),2.)*0.4, pow(max(color,0.),3.)*0.15 , 0);
            }else{
                gl_FragColor = vec4( color, pow(max(color,0.),2.)*0.4, pow(max(color,0.),3.)*0.15 , 1.0);
            }
        }
    `

	const geometry = new THREE.CircleGeometry(width)
	//创建RawShaderMaterial着色器材质
	const material = new THREE.ShaderMaterial({
		uniforms: {
			time: { value: 0 }
		},
		transparent: true,
		depthWrite: false,
		depthTest: false,
		side: THREE.DoubleSide,
		vertexShader,
		fragmentShader
	})
	const planeMesh = new THREE.Mesh(geometry, material)
	planeMesh.position.set(...position)
	planeMesh.rotation.x = 0.5 * Math.PI
	return planeMesh
}

export {
	useResizeObserver,
	RGBELoader,
	THREE,
	OrbitControls,
	MapControls,
	FirstPersonControls,
	EffectComposer,
	RenderPass,
	OutlinePass,
	ShaderPass,
	FXAAShader,
	CSS2DObject,
	CSS2DRenderer,
	CSS3DSprite,
	CSS3DObject,
	CSS3DRenderer,
	TWEEN,
	keyType,
	compassRotate,
	rendererFn,
	labelRendererFn,
	GLTFloaderFn,
	mixersFn,
	clock,
	textureLoader,
	setContent,
	cameraFn,
	collideCheck,
	cameraDistanceFn,
	cameraControlsTwwen,
	cameraTwwen,
	createOpacityWallMat,
	createFlowWallMat,
	creatWallByPath,
	creatDiffusion,
	creatFire
}
